package cz.masa.exifReader;

import java.io.*;
import java.util.*;

public class exifReader{

	private boolean exif_intel = false;
	private String soubor;
	private int tiff_head_beg;
	private byte[] app1;
	
	private Vector data;
		
	//-- Nacitana data ----------------------------
	// IFD0 (main image)
	// Model				String		Shows model number of digicam.
	// Make					String		Shows manufacturer of digicam.
	// DateTime				String		Date/Time of image was last modified.
	// ImageDescription			String		Describes image.
	// Orientation				Integer         The orientation of the camera relative to the scene, when the image was captured.
	// Software				String		Shows firmware(internal software of digicam) version number.
	// Copyright				String		Shows copyright information.
	
	// ExposureTime				Double		Exposure time (reciprocal of shutter speed). Unit is second.
	// FNumber				Double		The actual F-number(F-stop) of lens when the image was taken.
	// ExposureProgram			Integer         Exposure program that the camera used when image was taken.
	// ISOSpeedRatings			Integer         CCD sensitivity equivalent to Ag-Hr film speedrate.
	// DateTimeOriginal			String		Date/Time of original image taken.
	// CompressedBitsPerPixel		Double		The average compression ratio of JPEG (rough estimate).
	// ShutterSpeedValue                    Double		Shutter speed by APEX value.
	// ShutterSpeed_C			Double		Shutter speed. ShutterSpeedValue value's power of 2, then reciprocal. 
	// ApertureValue			Double		The actual aperture value of lens when the image was taken. Unit is APEX.
	// ApertureValue_C			Double		Ordinary F-number(F-stop), calculate ApertureValue value's power of root 2 (=1.4142). 
	// ExifImageWidth			Long		Size of main image.
	// ExifImageHeight			Long		Size of main image.
	// FocalLength				Double		Focal length of lens used to take image. Unit is millimeter.
	// Flash				Integer         '0' means flash did not fire, '1' flash fired, '5' flash fired but strobe return light not detected, '7' flash fired and strobe return light detected.

	//--------------------------------------------

	public Integer BytesToInt(byte a){
		Integer ret;
		Integer aI = new Integer(a);
		if(aI.intValue() < 0) ret = new Integer(256 + aI.intValue()); else ret = aI; 
		return ret;
	}
	
	public Object getParam(String param){
		for(int i=0; i<data.size();i++){
			paramVal pv = (paramVal)(data.elementAt(i));
			if(pv.m_param.compareTo(param) == 0) return pv.m_val;
		}
		return null;
	}
	
	public String getAllParams(){
		String ret = "";
		for(int i=0; i<data.size();i++){
			paramVal pv = (paramVal)(data.elementAt(i));
			ret += pv.m_param + ": " + pv.m_val + "\n";
		}
		return ret;	
	}

	public Integer BytesToInt(byte a, byte b, boolean intel){
		String aS = Integer.toHexString(BytesToInt(a).intValue());
		String bS = Integer.toHexString(BytesToInt(b).intValue());
		String parse;
		if(intel) parse = bS + aS; else parse = aS + bS;
		Integer ret = new Integer(Integer.parseInt(parse,16));
		return ret;
	}

	public Long BytesToInt(byte a, byte b, byte c, byte d, boolean intel){
		String aS = Integer.toHexString(BytesToInt(a).intValue());
		String bS = Integer.toHexString(BytesToInt(b).intValue());
		String cS = Integer.toHexString(BytesToInt(c).intValue());
		String dS = Integer.toHexString(BytesToInt(d).intValue());
		if(aS.length()==1) aS = "0"+aS;
		if(bS.length()==1) bS = "0"+bS;
		if(cS.length()==1) cS = "0"+cS;
		if(dS.length()==1) dS = "0"+dS;
		String parse;
		if(intel) parse = dS + cS + bS + aS; else parse = aS + bS + cS + dS;
		Long ret = new Long(Long.parseLong(parse,16));
		return ret;
	}
	
	public String readExifString(byte[] tagid, int beg, int maxcnt) throws Exception{
		boolean nasel = false;
		int j = 0;
		while(j < maxcnt && nasel == false){
			int akt_offset = (beg) + (j * 12);
			if((app1[akt_offset] == tagid[0] && app1[akt_offset + 1] == tagid[1]) || (app1[akt_offset] == tagid[1] && app1[akt_offset + 1] == tagid[0])){
				nasel = true;
				int ent_len = BytesToInt(app1[akt_offset + 4], app1[akt_offset + 5], app1[akt_offset + 6], app1[akt_offset + 7], exif_intel).intValue();
				//System.out.println("Len: " + ent_len);
				int ent_offset = BytesToInt(app1[akt_offset + 8], app1[akt_offset + 9], app1[akt_offset + 10], app1[akt_offset + 11], exif_intel).intValue();
				//System.out.println("Offset: " + ent_offset);
				String ent_val = "";
				for(int k=0; k < ent_len - 1; k++){
					ent_val = ent_val + (char)app1[tiff_head_beg + ent_offset + k];
				}
				//System.out.println("Val: " + ent_val);
				return ent_val;
			}
			j++;
		}
		throw new Exception("Tag not found");
	}

	public double readExifURational(byte[] tagid, int beg, int maxcnt) throws Exception{
		boolean nasel = false;
		int j = 0;
		while(j < maxcnt && nasel == false){
			int akt_offset = (beg) + (j * 12);
			if((app1[akt_offset] == tagid[0] && app1[akt_offset + 1] == tagid[1]) || (app1[akt_offset] == tagid[1] && app1[akt_offset + 1] == tagid[0])){
				nasel = true;
				int ent_len = BytesToInt(app1[akt_offset + 4], app1[akt_offset + 5], app1[akt_offset + 6], app1[akt_offset + 7], exif_intel).intValue();
				int ent_offset = BytesToInt(app1[akt_offset + 8], app1[akt_offset + 9], app1[akt_offset + 10], app1[akt_offset + 11], exif_intel).intValue();
				long num = BytesToInt(app1[tiff_head_beg + ent_offset + 0], app1[tiff_head_beg + ent_offset + 1], app1[tiff_head_beg + ent_offset + 2], app1[tiff_head_beg + ent_offset + 3], exif_intel).longValue();
				long den = BytesToInt(app1[tiff_head_beg + ent_offset + 4], app1[tiff_head_beg + ent_offset + 5], app1[tiff_head_beg + ent_offset + 6], app1[tiff_head_beg + ent_offset + 7], exif_intel).longValue();
				return ((double)num/(double)den);
			}
			j++;
		}
		throw new Exception("Tag not found");
	}	
	
	public double readExifSRational(byte[] tagid, int beg, int maxcnt) throws Exception{
		boolean nasel = false;
		int j = 0;
		while(j < maxcnt && nasel == false){
			int akt_offset = (beg) + (j * 12);
			if((app1[akt_offset] == tagid[0] && app1[akt_offset + 1] == tagid[1]) || (app1[akt_offset] == tagid[1] && app1[akt_offset + 1] == tagid[0])){
				nasel = true;
				int ent_len = BytesToInt(app1[akt_offset + 4], app1[akt_offset + 5], app1[akt_offset + 6], app1[akt_offset + 7], exif_intel).intValue();
				int ent_offset = BytesToInt(app1[akt_offset + 8], app1[akt_offset + 9], app1[akt_offset + 10], app1[akt_offset + 11], exif_intel).intValue();
				long num = BytesToInt(app1[tiff_head_beg + ent_offset + 0], app1[tiff_head_beg + ent_offset + 1], app1[tiff_head_beg + ent_offset + 2], app1[tiff_head_beg + ent_offset + 3], exif_intel).longValue();
				long den = BytesToInt(app1[tiff_head_beg + ent_offset + 4], app1[tiff_head_beg + ent_offset + 5], app1[tiff_head_beg + ent_offset + 6], app1[tiff_head_beg + ent_offset + 7], exif_intel).longValue();

				Long max4b = new Long(Long.parseLong("FFFFFFFF",16));
				if(num > (max4b.longValue()/2)) num = -(max4b.longValue() - num + 1);
				if(den > (max4b.longValue()/2)) den = -(max4b.longValue() - den + 1);
				return ((double)num/(double)den);
			}
			j++;
		}
		throw new Exception("Tag not found");
	}	
	
	public long readExifULong(byte[] tagid, int beg, int maxcnt) throws Exception{
		boolean nasel = false;
		int j = 0;
		while(j < maxcnt && nasel == false){
			int akt_offset = (beg) + (j * 12);
			if((app1[akt_offset] == tagid[0] && app1[akt_offset + 1] == tagid[1]) || (app1[akt_offset] == tagid[1] && app1[akt_offset + 1] == tagid[0])){
				nasel = true;
				long ent_val = BytesToInt(app1[akt_offset + 8], app1[akt_offset + 9], app1[akt_offset + 10], app1[akt_offset + 11], exif_intel).longValue();
				return ent_val;
			}
			j++;
		}
		throw new Exception("Tag not found");
	}	
	
	public int readExifUShort(byte[] tagid, int beg, int maxcnt) throws Exception{
		boolean nasel = false;
		int j = 0;
		while(j < maxcnt && nasel == false){
			int akt_offset = (beg) + (j * 12);
			if((app1[akt_offset] == tagid[0] && app1[akt_offset + 1] == tagid[1]) || (app1[akt_offset] == tagid[1] && app1[akt_offset + 1] == tagid[0])){
				nasel = true;
				int ent_val = BytesToInt(app1[akt_offset + 8], app1[akt_offset + 9], exif_intel).intValue();
				return ent_val;
			}
			j++;
		}
		throw new Exception("Tag not found");
	}	


	public exifReader(String soubor){
		this.soubor = soubor;
		data = new Vector(30);
	}

	public int readExif(){
		try{
			File inputFile = new File(soubor);
			FileInputStream in = new FileInputStream(inputFile);
			BufferedInputStream inB = new BufferedInputStream(in);

			byte[] begimg = new byte[6];
		
			inB.read(begimg,0,6);

			if(begimg[0] == (byte)0xFF && begimg[1] == (byte)0xD8 && begimg[2] == (byte)0xFF && begimg[3] == (byte)0xE1){
				System.out.println("JPEG s APP1");
				int app1_length = BytesToInt(begimg[4], begimg[5], false).intValue();
				System.out.println("Len APP1: " + app1_length);
				app1 = new byte[app1_length];
				inB.read(app1,0,app1_length);
				
				int i = 0;
				boolean nasel = false;
				while(i < app1_length - 10 && nasel == false){
					if(app1[i] == (byte)0x45 && app1[i+1] == (byte)0x78 && app1[i+2] == (byte)0x69 && app1[i+3] == (byte)0x66){
						System.out.println("Exif pritomen");
						nasel = true;
					}
					i++;
				}
				tiff_head_beg = i + 5;
				
				if(app1[tiff_head_beg] == (byte)0x49){
					exif_intel = true;
					System.out.println("Intel");
				}
				
				int pocet_ifd_dir_entry = BytesToInt(app1[tiff_head_beg + 8], app1[tiff_head_beg + 9], exif_intel).intValue();
				System.out.println("IDF entry: "+pocet_ifd_dir_entry);
				
				byte tag[] = new byte[2];
				
				// -- IFD0 (main image) -------------------------
				try {
					tag[0] = (byte)0x01; tag[1] = (byte)0x10;
					String pom = readExifString(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
                                        System.out.println(pom);
					data.add(new paramVal("Model",pom));
				}
				catch(Exception ex){  }
				
				try {
					tag[0] = (byte)0x01; tag[1] = (byte)0x0f;	
					String pom = readExifString(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
					data.add(new paramVal("Make",pom));
				}
				catch(Exception ex){ }
				
				try{
					tag[0] = (byte)0x01; tag[1] = (byte)0x32;
					String pom = readExifString(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
					data.add(new paramVal("DateTime",pom));
				}
				catch(Exception ex){ }

				try{
					tag[0] = (byte)0x01; tag[1] = (byte)0x0e;
					String pom = readExifString(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
					data.add(new paramVal("ImageDescription",pom));
				}
				catch(Exception ex){ }	
				
				try{
					tag[0] = (byte)0x01; tag[1] = (byte)0x12;
					Integer pom = new Integer(readExifUShort(tag,tiff_head_beg + 10,pocet_ifd_dir_entry));
					data.add(new paramVal("Orientation",pom));
				}
				catch(Exception ex){ }			

				try{
					tag[0] = (byte)0x01; tag[1] = (byte)0x31;
					String pom = readExifString(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
					data.add(new paramVal("Software",pom));
				}
				catch(Exception ex){ }	
				
				try{
					tag[0] = (byte)0x82; tag[1] = (byte)0x98;
					String pom = readExifString(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
					data.add(new paramVal("Copyright",pom));
				}
				catch(Exception ex){ }	
				
				// -- Exif SubIFD -----------------------------				
				int exifSubOff = 0;
				int exifSubCnt = 0;	
				try{
					tag[0] = (byte)0x87; tag[1] = (byte)0x69;
					exifSubOff = (int)readExifULong(tag,tiff_head_beg + 10,pocet_ifd_dir_entry);
					exifSubCnt = BytesToInt(app1[tiff_head_beg + exifSubOff], app1[tiff_head_beg + exifSubOff + 1], exif_intel).intValue();
				}
				catch(Exception ex){ System.out.println("Problem with EXIF Sum section..."); }

				try{
					tag[0] = (byte)0x82; tag[1] = (byte)0x9A;
					Double pom = new Double(readExifURational(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ExposureTime",pom));
				}
				catch(Exception ex){ }

				try{
					tag[0] = (byte)0x82; tag[1] = (byte)0x9D;
					Double pom = new Double(readExifURational(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("FNumber",pom));
				}
				catch(Exception ex){ }

				try{
					tag[0] = (byte)0x88; tag[1] = (byte)0x22;
					Integer pom = new Integer(readExifUShort(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ExposureProgram",pom));
				}
				catch(Exception ex){ }
				
				try{
					tag[0] = (byte)0x88; tag[1] = (byte)0x27;
					Integer pom = new Integer(readExifUShort(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ISOSpeedRatings",pom));
				}
				catch(Exception ex){ }				
				
				try{
					tag[0] = (byte)0x90; tag[1] = (byte)0x03;
					String pom = readExifString(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt);
					data.add(new paramVal("DateTimeOriginal",pom));
				}
				catch(Exception ex){ }				
				
				try{
					tag[0] = (byte)0x91; tag[1] = (byte)0x02;
					Double pom = new Double(readExifURational(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("CompressedBitsPerPixel",pom));
				}
				catch(Exception ex){ }				
				
				try{
					tag[0] = (byte)0xa0; tag[1] = (byte)0x02;
					Long pom = new Long(readExifULong(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ExifImageWidth",pom));
				}
				catch(Exception ex){ }

				try{
					tag[0] = (byte)0xa0; tag[1] = (byte)0x03;
					Long pom = new Long(readExifULong(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ExifImageHeight",pom));
				}
				catch(Exception ex){ }

				try {
					tag[0] = (byte)0x92; tag[1] = (byte)0x01;
					Double pom = new Double(readExifSRational(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ShutterSpeedValue",pom));
					double ShutterSpeed = 1.0 / Math.pow(2.0,pom.doubleValue());
					data.add(new paramVal("ShutterSpeed_C",new Double(ShutterSpeed)));
				}
				catch(Exception ex){ }

				try{
					tag[0] = (byte)0x92; tag[1] = (byte)0x02;
					Double pom = new Double(readExifURational(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("ApertureValue",pom));
					double ApertureValue = Math.pow(1.4142,pom.doubleValue());
					data.add(new paramVal("ApertureValue_C",new Double(ApertureValue)));
				}
				catch(Exception ex){ }

				try{
					tag[0] = (byte)0x92; tag[1] = (byte)0x0A;
					Double pom = new Double(readExifURational(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("FocalLength",pom));
				}
				catch(Exception ex){ }
				
				try{
					tag[0] = (byte)0x92; tag[1] = (byte)0x09;
					Integer pom = new Integer(readExifUShort(tag,tiff_head_beg + exifSubOff + 2,exifSubCnt));
					data.add(new paramVal("Flash",pom));
				}
				catch(Exception ex){ }	
				
			} else {
				return -1;
			}
			inB.close();
		}
		catch(java.io.IOException ex){ return -2; }
		return 1;
	}
	
	
	public static void main(String[] args) {
		System.out.println("- Exif reader -----------");
		//exifReader erdr = new exifReader(args[0]);
                exifReader erdr = new exifReader("C:/IMGP_1_30.JPG");
		int ret = erdr.readExif();
		if(ret > 0) System.out.println(erdr.getAllParams()); else System.out.println("Exif not found");
	}
}

class paramVal{
	public String m_param;
	public Object m_val;
	
	public paramVal(String param,Object val){
		m_param = param;
		m_val = val;
	}
}